import {
    AfterViewInit,
    Component,
    ElementRef,
    Input,
    OnChanges,
    OnInit,
    ViewChild,
    HostListener
} from '@angular/core';
import { Volcano } from './model/volcano-model';
import { Ball } from './model/ball-model';
import { Subscription } from "rxjs/Subscription";
import { Observable } from "rxjs/Observable";

const time = 20;
@Component({
    selector: 'volcano',
    templateUrl: "./volcano.component.html",
    styleUrls: ['./volcano.component.css']
})

export class VolcanoComponent implements OnInit {

    private canvasContainer: any;

    @ViewChild('canvas') canvas: ElementRef;

    public volcano: Volcano = new Volcano();
    public balls: Ball[] = [];
    public textBall: Ball;

    private subscription: Subscription;

    private canvasWidth: number;
    private canvasHeight: number;

    constructor(private _elementRef: ElementRef) {
    }

    ngOnInit() {
        this.volcano.coefficient = 2;
        this.volcano.hillside = 0.4;
        this.volcano.mouthWidth = 0.3;
        this.volcano.state = 0;
        this.volcano.eruptRate = 1;
        this.canvasContainer = Array.from(this._elementRef.nativeElement.children).filter(child => child["className"] === 'volcano-container')[0];
        this.handleEl();
        //火山状态变迁：山体膨胀系数，岩浆鼓包位置，状态变化持续时间，剩余岩浆比例，山体抖动，喷射力量
        const states = [{
            coefficient: 3, hillside: 0.3, time: 2000, eruptRate: 0, shake: false, strenth: 0.3
        },
        {
            coefficient: 3, hillside: 0.4, time: 2000, eruptRate: 1, shake: false, strenth: 0.3
        },
        {
            coefficient: 0.9, hillside: 0.4, time: 500, eruptRate: 1, shake: false, strenth: 0.3
        },
        {
            coefficient: 0.9, hillside: 0.85, time: 500, eruptRate: 1, shake: true, strenth: 0.3
        },
        {
            //第一次爆发
            coefficient: 0.9, hillside: 0.9, time: 200, eruptRate: 1, shake: false, strenth: 0.3
        },
        {
            coefficient: 2, hillside: 0.4, time: 600, eruptRate: 0.4, shake: false, strenth: 0.3
        },
        {
            coefficient: 3, hillside: 0.3, time: 3000, eruptRate: 0, shake: false, strenth: 0.5
        },
        {
            coefficient: 3, hillside: 0.4, time: 2000, eruptRate: 1, shake: false, strenth: 0.5
        },
        {
            coefficient: 0.7, hillside: 0.4, time: 500, eruptRate: 1, shake: false, strenth: 0.5
        },
        {
            coefficient: 0.7, hillside: 0.85, time: 500, eruptRate: 1, shake: true, strenth: 0.5
        },
        {
            //第二次爆发
            coefficient: 0.7, hillside: 0.9, time: 200, eruptRate: 1, shake: false, strenth: 0.5
        },
        {
            coefficient: 2, hillside: 0.4, time: 600, eruptRate: 0.4, shake: false, strenth: 0.5
        }, {
            coefficient: 3, hillside: 0.3, time: 3000, eruptRate: 0, shake: false, strenth: 1
        },
        {
            coefficient: 3, hillside: 0.4, time: 3000, eruptRate: 1, shake: false, strenth: 1
        },
        {
            coefficient: 0.7, hillside: 0.4, time: 500, eruptRate: 1, shake: false, strenth: 1
        },
        {
            coefficient: 0.7, hillside: 0.85, time: 1500, eruptRate: 1, shake: true, strenth: 1
        },
        {
            //第三次爆发
            coefficient: 0.3, hillside: 0.9, time: 400, eruptRate: 1, shake: false, strenth: 1
        },
        {
            coefficient: 2, hillside: 0.4, time: 2000, eruptRate: 0.4, shake: false, strenth: 1
        },
        {
            coefficient: 3, hillside: 0.3, time: 2000, eruptRate: 0, shake: false, strenth: 0.5
        },
        {
            coefficient: 3, hillside: 0.4, time: 500, eruptRate: 0.3, shake: false, strenth: 0.5
        },
        {
            coefficient: 2.9, hillside: 0.39, time: 0, eruptRate: 0, shake: false, strenth: 0.5
        }]
        //404文字持续时间 2000ms
        const textNum = 2000 / time;
        let drawText = 0;
        const canvasEl: HTMLCanvasElement = this.canvas.nativeElement;
        const ctx: CanvasRenderingContext2D = canvasEl.getContext('2d');
        this.subscription = Observable.interval(time).subscribe(() => {
            this.clean(ctx, canvasEl);
            let nextState = (this.volcano.state + 1) % states.length;
            let current = states[this.volcano.state];
            let next = states[nextState];
            if (next.eruptRate < current.eruptRate) {
                //火山爆发
                let rate = (next.coefficient - this.volcano.coefficient) / (next.coefficient - current.coefficient);
                this.erupt(rate, current.eruptRate, next.eruptRate, current.strenth);
            }
            if((nextState == 17 || drawText > 0) && drawText <= textNum){
                //404爆出
                drawText ++;
                if(!this.textBall){
                    this.textBall = this.balls.pop();
                }
                
            }
            if(nextState != (states.length - 1)){
                //状态保持最后的喷射状态，作为结束
                this.volActivity(current, next, current.time / time);
                if (this.passed(states[this.volcano.state], states[nextState])) {
                    this.volcano.state = nextState;
                    this.volcano.eruptRate = next.eruptRate;
                }
            }
            this.volcano.shake = current.shake ? current.strenth : 0;
            //绘制火山
            this.drawVolcano(ctx, canvasEl);
            //绘制岩浆小球
            this.drawBalls(ctx);
            //绘制404文字
            this.drawText(ctx, drawText / textNum);
        });
    }

    @HostListener('window:resize', ['$event'])
    onResize(event) {
        this.handleEl();
    }

    //动态调整画布大小
    private handleEl() {
        let width = this.canvasContainer['offsetWidth'];
        let height = this.canvasContainer['offsetHeight'];
        const canvasEl: HTMLCanvasElement = this.canvas.nativeElement;
        canvasEl.width = width;
        canvasEl.height = Math.min(width / 2, height * 0.8);
    }

    //火山属性初始化
    private initVolcano(el: HTMLCanvasElement) {
        this.volcano.x = el.width / 2;
        this.volcano.width = el.width * 0.15;
        this.volcano.height = el.height * 0.2;
        this.volcano.fillColor = '#384265';
    }
    //绘制火山
    private drawVolcano(ctx: CanvasRenderingContext2D, el: HTMLCanvasElement) {
        const volMouthWidth = this.volcano.mouthWidth;
        const hillside = this.volcano.hillside;
        const coefficient = this.volcano.coefficient;
        this.initVolcano(el);
        ctx.beginPath();
        ctx.fillStyle = this.volcano.fillColor;
        let loc = {
            x: this.volcano.x - this.volcano.width / 2,
            y: el.height
        }
        if (this.volcano.shake > 0) {
            loc.x += this.volcano.width * 0.1 * (0.5 - Math.random()) * this.volcano.shake;
        }
        let mouseLeft = this.volcano.width * (1 - volMouthWidth) / 2;
        let hillsideX = mouseLeft * coefficient * hillside;
        let hillsideY = this.volcano.height * hillside;
        let mouseCoe = Math.max(this.volcano.width * volMouthWidth * 0.3 * (1 - coefficient), 0);
        ctx.bezierCurveTo(loc.x, loc.y,
            loc.x + hillsideX, loc.y - hillsideY,
            loc.x + mouseLeft, loc.y - this.volcano.height)
        ctx.quadraticCurveTo(loc.x + this.volcano.width / 2, loc.y - this.volcano.height - mouseCoe,
            loc.x + this.volcano.width - mouseLeft, loc.y - this.volcano.height);
        ctx.quadraticCurveTo(loc.x + this.volcano.width - hillsideX, loc.y - hillsideY,
            loc.x + this.volcano.width, loc.y);
        ctx.lineTo(loc.x, loc.y)
        ctx.fill();
    }

    //火山爆发，即创建岩浆小球，以供绘制使用
    private erupt(rate: number, current: number, next: number, strenth: number) {
        //按照一定计算规则设置随着喷射活动进行进度
        let srate = ((1 - current) + (current - next) * (1 - rate));
        //按照进度设置每次爆发的时候需要产生的球的数量
        let ballNum = 15 - 15 * srate;
        const origin = {
            x: this.canvas.nativeElement.width / 2,
            y: this.canvas.nativeElement.height - this.volcano.height,
            vx: this.volcano.width * 2.2 / 1500 * time,
            vy: (-17 + srate * 3) * strenth //按照进度动态调整每次爆发的时候的小球的竖直方向的速度
        }
        if (ballNum > 0) {
            for (let i = 0; i < ballNum; i++) {
                let rand = 0.5 - Math.random();
                let ball = new Ball();
                //火山内口比例为0.5，随机数产生内口范围内的小球
                ball.x = origin.x + rand * this.volcano.mouthWidth * this.volcano.width * 0.5;
                ball.xv = origin.vx * rand;
                ball.y = origin.y;
                ball.yv = origin.vy;
                ball.width = ball.height = 5;
                ball.op = 1;
                ball.color = "rgba(" + Math.floor(Math.random() * 255) + ","
                    + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + ",";
                this.balls.push(ball);
            }
        }
    }
    //绘制小球
    private drawBalls(ctx: CanvasRenderingContext2D) {
        if (this.balls.length == 0) {
            return;
        }
        for (let i = this.balls.length - 1; i >= 0; i--) {
            let ball = this.balls[i];
            if (ball.op <= 0) {
                this.balls.splice(0, i + 1);
                break;
            }
            this.drawBall(ctx, ball);
            this.updateBall(ball);
        }
    }
    private drawBall(ctx: CanvasRenderingContext2D, ball: Ball) {
        ctx.beginPath();
        ctx.arc(ball.x, ball.y, ball.width, ball.height, Math.PI * 2, true);
        ctx.closePath();
        ctx.fillStyle = ball.color + ball.op + ")";
        ctx.fill();
    }
    private updateBall(ball: Ball) {
        //显示3秒，三秒后透明
        ball.op = ball.op - time / 3000;
        ball.x = ball.x + ball.xv;
        ball.y = ball.y + ball.yv;
        //调整小球竖直方向的速度，即重力
        ball.yv = ball.yv + 0.3;
    }
    //火山运动，膨胀系数的变化，岩浆鼓包的位置变化
    private volActivity(state1: {}, state2: {}, times: number) {
        this.volcano.coefficient += (state2["coefficient"] - state1["coefficient"]) / times;
        this.volcano.hillside += (state2["hillside"] - state1["hillside"]) / times;
    }
    //判断是否到达状态变化末尾，是则返回true，然后进行下一阶段的火山运动
    private passed(state1: {}, state2: {}): boolean {
        if ((state1["coefficient"] - this.volcano.coefficient) * (state2["coefficient"] - this.volcano.coefficient) < 0
            || (state1["hillside"] - this.volcano.hillside) * (state2["hillside"] - this.volcano.hillside) < 0) {
            return false;
        }
        this.volcano.coefficient = state2["coefficient"];
        this.volcano.hillside = state2["hillside"];
        return true;
    }
    //绘制404,用小球模拟喷射后的自由落体运动
    private drawText(ctx: CanvasRenderingContext2D, rate: number){
        if(rate == 0){
            return;
        }
        let loc = {
            x: this.canvas.nativeElement.width / 2,
            y: this.textBall.y
        }
        ctx.save();
        ctx.fillStyle = "white";
        ctx.textAlign = "center";
        ctx.font = "bold " + Math.floor(this.volcano.width * this.volcano.mouthWidth / 3) * (Math.pow(rate, 2) * 4 + 1) + "px Georgia";
        ctx.translate(loc.x, loc.y);
        ctx.rotate(Math.PI * (2 * rate * 0.7 + 0.5));
        ctx.fillText("404", 0, 0);
        ctx.restore();
        if(rate < 1){
            this.textBall.y = this.textBall.y + this.textBall.yv;
            this.textBall.yv = this.textBall.yv + 0.3;
        }
    }
    //清空画布
    private clean(ctx: CanvasRenderingContext2D, el: HTMLCanvasElement) {
        ctx.clearRect(0, 0, el.offsetWidth, el.offsetHeight);
    }
    //结束，父组件调用，否则在flappy-bird中会继续进行绘制，占用资源
    private stop(){
        this.subscription.unsubscribe();
    }
}
